Release 10.1A: OpenEdge Development:
Progress 4GL Handbook


Defining h-OrderCalcs.p to calculate totals

This section describes how to create a sample subprocedure, called h-ordercalcs.p, to calculate the totals for the price and extended price for all Order lines.

To create the first subprocedure:

  1. Open a New Procedure Window.
  2. Enter this 4GL code:
  3. /* h-OrderCalcs.p */ 
    DEFINE INPUT  PARAMETER piOrderNum      AS INTEGER    NO-UNDO. 
    DEFINE OUTPUT PARAMETER pdOrderPrice    AS DECIMAL    NO-UNDO. 
    DEFINE OUTPUT PARAMETER pdOrderTotal    AS DECIMAL    NO-UNDO. 
    FIND Order WHERE Order.OrderNum = piOrderNum. 
         
    /* Add up the total price and the extended price for all order lines. */ 
    FOR EACH OrderLine OF Order: 
        ASSIGN pdOrderTotal = pdOrderTotal + OrderLine.ExtendedPrice 
               pdOrderPrice = pdOrderPrice + OrderLine.Price * OrderLine.Qty.    
    END. 
    

  4. Save this code as h-OrderCalcs.p.

The h-CustOrderWin2.w test window will call this procedure. This procedure has an INPUT parameter, which is an Order number, and it passes back two OUTPUT parameters. It uses a naming convention that begins each parameter with the letter p to help identify them throughout the procedure.

The code finds the Order record for the Order number passed in, and then in a FOR EACH block, it reads each OrderLine for the Order and adds up two sets of values. The first value, stored in pdOrderTotal, is the total of all the ExtendedPrice values for the OrderLines. The ExtendedPrice is the price after the Customer’s discount has been factored in. The second value, pdOrderPrice, is simply the total of the raw price for the OrderLine before the discount, which is the Price field times the Qty (quantity) field.

After the FOR EACH block, the procedure ends and returns the output parameters to the caller.

Notice that the FIND statement can be a unique find (without a qualifier such as FIRST) because there can be only one Order record with a given OrderNum value.

Next there’s a new statement type in the FOR EACH block: the ASSIGN statement. Any time you are assigning more than one value at a time, as the code is here, it is more efficient to use this statement, which can contain any number of assignments, each using the equal sign, and a single period at the end.

Checking the syntax of a procedure

If you try to run the procedure directly by pressing F2, you get the error message shown in Figure 7–10.

Figure 7–10: Mismatched parameters error message

This error message appears because the procedure expects parameters and you’re not passing it any when you just run it from the editor. However, you can check whether your syntax is valid by pressing SHIFT+F2 or selecting Compile Check Syntax from the menu. The information box shown in Figure 7–11 appears.

Figure 7–11: Correct syntax information box

After you verify that the syntax is correct, continue on to write the code that calls the procedure.

Adding fill-ins to the window for the calculations

In this section you add fill-in fields to your sample window for Order calculations.

To write the code that calls h-OrderCalcs.p:

  1. Open h-CustOrderWin2.w in the AppBuilder.
  2. Stretch the window somewhat at the bottom to make room for some more fields.
  3. To place fill-in fields on your window to hold the totals the procedure passes back, click the Fill-In icon on the Palette:
  4. Click under the browse in your design window to drop the fill-in field onto the window.
  5. Repeat Steps 3 and 4 twice more to get a total of three new fill-in fields.
  6. Alternatively, you can click twice on the Fill-In icon to lock it in selected mode, and then click three times on the window to get the three fill-ins. The icon shows a lock symbol to show the mode it’s in:

    If you lock the Fill-In icon, deselect it when you’re done adding the three fields by clicking the pointer icon on the Palette.

  7. In the AppBuilder main window, give the three fields these names and labels, respectively:
    • dTotalPrice and Order Total.
    • dTotalExt and Total Ext Price.
    • dAvgDisc and Average Discount.
Changing field properties in the property sheet

To edit the properties of the fill-in fields:

  1. Double-click on a fill-in field to bring up its property sheet.
  2. In the Define As drop-down list, change the data type to Decimal.
  3. Check the toggle boxes to make the field Enabled but Read-Only. These options provide a shadow box around each value but prevent the user form modifying the field value.
  4. Change the field Width to 16:
  5. Click OK.
  6. Repeat Steps 1 through 5 for the other two fill-in fields.
  7. The design window should look roughly like this when you’re done:

Writing a VALUE-CHANGED trigger for the browse

Now you need to write the code to run the h-OrderCalcs.p procedure. When does this need to happen? Whenever a new row is selected in the list of Orders for a Customer. This is called the VALUE-CHANGED event for the browse.

To write the trigger for the browse:

  1. Click on the browse to select it, then click the Edit Code icon in the toolbar. The Section Editor appears.
  2. The default code block to define for a browse is the VALUE-CHANGED trigger, so this comes up automatically.

  3. Fill in the empty DO-END block with this code:
  4. DO: 
         RUN h-OrderCalcs.p 
              (INPUT Order.OrderNum, 
              OUTPUT dTotalPrice, OUTPUT dTotalExt). 
         dAvgDisc =  (dTotalPrice - dTotalExt) / dTotalPrice. 
         DISPLAY dTotalPrice dTotalExt dAvgDisc  
              WITH FRAME CustQuery. 
    END. 
    

This code runs the procedure, passes in the current Order number, and gets back the two values that map to the fill-in fields you defined. These are really just variables, but the AppBuilder generates definitions with a special phrase added to turn the variable into a value that can be displayed in the window as a proper GUI control. This is the VIEW-AS phrase and here is one of the three variable definitions the AppBuilder generates:

DEFINE VARIABLE dTotalPrice AS DECIMAL FORMAT "->>,>>9.99":U INITIAL 0  
     LABEL "Order Total"  
     VIEW-AS FILL-IN  
     SIZE 14 BY 1 NO-UNDO. 

The SIZE width BY height phrase defines the display size of the fill-in field. A DEFINE VARIABLE statement can specify all the different kinds of ways a single field value can be displayed, including toggle boxes for logical values, selection lists, and editor controls, using the VIEW-AS phrase. You’ll learn a lot more about this phrase in Chapter 8, " Defining Graphical Objects."

After the RUN statement, the code does a calculation of its own to determine the average discount and stores that value in the third fill-in field:

dAvgDisc =  (dTotalPrice - dTotalExt) / dTotalPrice. 

Finally, the code displays the three fields in the window’s frame:

DISPLAY dTotalPrice dTotalExt dAvgDisc  
     WITH FRAME CustQuery. 

As you can see, this DISPLAY statement looks exactly like the DISPLAY statements you’ve been using in the little examples earlier in the chapter. But because the fields are given the specific display type of FILL-IN, they show up as GUI controls rather than just in the report-like format you get by default.

To see the effects of your changes, Run the procedure. When you first run it, the fields come up with zeroes in them. But when you click on another row of the browse, then the calculations appear correctly:

The VALUE-CHANGED event happens only when you actually select a row in the browse, not when it is first populated with data. To correct this, you need to apply that event when the window is first initialized and also whenever the user selects a different Customer.

Adding APPLY statements to the procedure

This section provides an example of how you can use the APPLY statement to cause an event.

To add an APPLY statement to your test window procedure:

  1. Add this code to the main block of the window procedure:
  2. MAIN-BLOCK: 
    DO ON ERROR   UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK 
         ON END-KEY UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK: 
         RUN enable_UI. 
         APPLY "VALUE-CHANGED" TO OrderBrowse. 
            IF NOT THIS-PROCEDURE:PERSISTENT THEN 
            WAIT-FOR CLOSE OF THIS-PROCEDURE. 
    END. 
    

    The APPLY statement causes an event to happen programmatically, just as the user action of clicking on a browse row would cause the event. Now the event is fired when the window first comes up.

  3. Add the same APPLY statement to each of the four trigger blocks for the First, Prev, Next, and Last buttons, after the Order query is opened. For example:
  4. DO: 
      GET NEXT CustQuery. 
      IF AVAILABLE Customer THEN  
      DO: 
       DISPLAY Customer.CustNum Customer.Name Customer.Address Customer.City  
         Customer.State 
              WITH FRAME CustQuery IN WINDOW CustWin. 
         {&OPEN-BROWSERS-IN-QUERY-CustQuery} 
         APPLY "VALUE-CHANGED" TO OrderBrowse. 
      END. 
    END. 
    

Now the event is fired each time there’s a different Customer, when the Order query is reopened.

It would be helpful to have this behavior become a part of every navigation button automatically so that you didn’t have to enter this line of code all over the place. For now your modified window procedure should work properly in all the places where the VALUE-CHANGED event must occur.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095